package com.lhj.demo.simple.aspect;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.lhj.demo.simple.annotation.Log;
import com.lhj.demo.simple.domain.dto.QueryDTO;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * @author hj.luo
 * @Date 2023-07-19 10:08
 * @descripition
 * @version: 1.0
 */
@Aspect
@Component
public class LogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);

    // 定义切点
    @Pointcut("@annotation(log)")
    public void pointcut(Log log) {
        // 切点定义，此处不含具体逻辑
    }

    // 定义切点2
    @Pointcut("@annotation(com.lhj.demo.simple.annotation.Log)")
    public void pointcutTwo() {
        // 切点定义，此处不含具体逻辑
        /**
         * 切点2更加灵活。如果单单需要@Log 下的方法作为切面可以灵活调整
         * 例如:  @Pointcut("@annotation(com.lhj.demo.simple.annotation.Log) || @annotation(com.lhj.demo.simple.annotation.Auth)")
         *
         */
    }

    @AfterReturning(pointcut = "pointcutTwo()", returning = "result")
    public void afterReturningAdviceTwo(JoinPoint joinPoint, Object result) {
        try {
            // 使用反射方式获取注解参数
            Log annotationLog = getClassAnnotation(joinPoint, Log.class);
            LOGGER.info("afterReturningAdviceTwo-module:{}", annotationLog.module());
            LOGGER.info("afterReturningAdviceTwo-operatorType:{}", annotationLog.operatorType());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 在目标方法成功返回结果后执行的逻辑
        LOGGER.info("AfterReturning 在目标方法成功返回结果后执行的逻辑");
    }


    // 前置通知
    @Before("pointcut(log)")
    public void beforeAdvice(JoinPoint joinPoint, Log log) {
        LOGGER.info("前置通知");
        Object[] args = joinPoint.getArgs();
        String type = (String) args[0];
        QueryDTO arg = (QueryDTO) args[1];
        LOGGER.info(type);
        LOGGER.info(arg.toString());
    }

    // 后置通知
    @After("pointcut(log)")
    public void afterAdvice(JoinPoint joinPoint, Log log) {
        // 在目标方法执行后执行的逻辑，无论是否发生异常
        LOGGER.info("After 在目标方法执行后执行的逻辑，无论是否发生异常");
    }

    // 返回通知
    @AfterReturning(pointcut = "pointcut(log)", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result, Log log) {
        LOGGER.info("AfterReturning-result:{}", JSON.toJSONString(result));
        LOGGER.info("AfterReturning-joinPoint:{}", JSONUtil.toJsonStr(joinPoint));
        // 在目标方法成功返回结果后执行的逻辑
        LOGGER.info("AfterReturning 在目标方法成功返回结果后执行的逻辑");
    }

    // 异常通知
    @AfterThrowing(pointcut = "pointcut(log)", throwing = "exception")
    public void afterThrowingAdvice(Exception exception, Log log) {
        // 在目标方法抛出异常后执行的逻辑
        LOGGER.info("AfterThrowing 在目标方法抛出异常后执行的逻辑");
    }

    @AfterThrowing(pointcut = "pointcut(log)", throwing = "throwable")
    public void afterThrowable(Throwable throwable, Log log) {
        // 在目标方法抛出异常后执行的逻辑
        LOGGER.info("afterThrowable 在目标方法抛出异常后执行的逻辑");
    }

    // 环绕通知
    @Around("pointcut(log)")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        // 在目标方法执行前后执行的逻辑，可控制目标方法的执行过程
        LOGGER.info("Around 在目标方法执行前后执行的逻辑，可控制目标方法的执行过程");
        Object result = null;
        try {
            // 前置处理逻辑
            LOGGER.info("Around 前置处理逻辑");
            result = joinPoint.proceed();
            // 后置处理逻辑
            LOGGER.info("Around 后置处理逻辑");
        } catch (Throwable throwable) {
            // 异常处理逻辑
            LOGGER.info("Around 异常处理逻辑");
            throw throwable;
        } finally {
            // 最终处理逻辑
            LOGGER.info("Around 最终处理逻辑");
        }
        return result;
    }

    @SuppressWarnings("SameParameterValue")
    private static <T extends Annotation> T getClassAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(annotationClass);
        }
        return null;
    }
}
